<?php

class AuthGenerator extends CApplicationComponent
{
    /**
     * the seperator of the authItem name 
     * Ex: the controller names AdminController and the action names viewAction then the authItem names admin_view
     */
    const SEPERATOR = '_';
    
    /**
     * An instance of IAuthManager
     * @var IAuthManager
     */
    protected $_auth;
    
    public $authComponent;

    /**
     * the controller paths array
     * @var array
     * Ex.array(
     *   'path1',
     *   'path2',
     *   'moduleName1'=>'path3',  //if the key not number that it means it's a module path and the key is module's name.
     *   'moduleName2'=>'path4',
     *   ...
     * )
     */
    public $controllerPaths = array();
    
    public function init()
    {
        parent::init();
        
        if(empty($this->authComponent))
            $this->setAuthManager(Yii::app()->authManager);        
        else
        {
            $auth = Yii::app()->getComponent($this->auth);
            $this->setAuthManager($auth);
        }
    }
    
    public function setAuthManager($authManager)
    {
        if($authManager instanceof IAuthManager)
            $this->_auth = $authManager;
        else
            throw new CException('The auth manager must be an instance of IAuthManager');
    }
    
    public function getAuthManager()
    {
        return $this->_auth;
    }
    
    public function addControllerPath($controllerPath, $moduleName='')
    {
        if(empty($moduleName))
            $this->controllerPaths[] = $controllerPath;
        else
            $this->controllerPaths[$moduleName] = $controllerPath;
    }
    
    public function getControllerPaths()
    {
        return $this->controllerPaths;
    }
    
    public function run()
    {
        $controllerPaths = $this->getControllerPaths();
        if(empty($controllerPaths))
           throw new CException('the controllerPaths can\'t be empty');
        
        foreach($controllerPaths as $moduleName => $controllerPath)
        {
            $controllersActions = $this->getControllersActionsFromPath($controllerPath);
            $this->addAuthItems($controllersActions, $moduleName);
        }
        
        $this->getAuthManager()->save();
    }
    
    protected function addAuthItems($controllersActions, $moduleName='')
    {
        if( ! empty($controllersActions))
        {
            foreach($controllersActions as $controllerName => $actionNames)
            {
                $roleName = self::getRoleName($controllerName, $moduleName);
                if( ! ($role = $this->getAuthManager()->getAuthItem($roleName)))
                    $role = $this->getAuthManager()->createAuthItem($roleName, CAuthItem::TYPE_ROLE);
                
                $this->addAuthItem($controllerName, $actionNames, $moduleName);
                $controllerTaskName = self::getTaskName($controllerName, $moduleName);
                
                if( ! $role->hasChild($controllerTaskName) && $this->getAuthManager()->getAuthItem($controllerTaskName))
                    $role->addChild($controllerTaskName);
            }
        }
    }
    
    public function addAuthItem($controllerName, $actionNames, $moduleName='')
    {
        if( ! empty($controllerName) && ! empty($actionNames))
        {
            foreach($actionNames as $actionName)
                $this->addAuthItemFromAction($controllerName, $actionName, $moduleName);
        }
    }
    
    public function addAuthItemFromAction($controllerName, $actionName, $moduleName='')
    {
        $controllerTaskName = self::getTaskName($controllerName, $moduleName);
        $authItemController = $this->getAuthManager()->getAuthItem($controllerTaskName);
        if(empty($authItemController))
            $authItemController = $this->getAuthManager()->createTask($controllerTaskName);
        
        $authAccessAction = self::getOperationName($controllerName, $actionName, $moduleName);
        if( ! $this->getAuthManager()->getAuthItem($authAccessAction))
            $this->getAuthManager()->createOperation($authAccessAction);

        if( ! $authItemController->hasChild($authAccessAction))
            $authItemController->addChild($authAccessAction);
    }
    
    /**
     * get all controller actions
     * @param string $path
     * @return array array(
     *    'controllerName1'=>array('action11', 'action12', ...),
     *    'controllerName2'=>array('action21', 'action22', ...),
     *    ...
     * )
     */
	public function getControllersActionsFromPath($path)
    {
        $controllers = array();
        foreach(glob($path.DS.'*Controller.php') as $controllerFile)
        {
            $controller = $this->getControllerActionsFromFilePath($controllerFile);
            if( ! empty($controller) && is_array($controller))
                $controllers = array_merge($controllers, $controller);
        }
        
        return $controllers;
    }
    
    /**
     * get a controller actions
     * @param string $controllerFilePath a directory of controller.
     * @return mixed array or false array(
     *     'controllerName'=>array('action1', 'action2', ...)
     * )
     */
    public function getControllerActionsFromFilePath($controllerFilePath)
    {
        $file = fopen($controllerFilePath, 'r');
        $controllerClassName = '';
        $actions = array();
        while(false === feof($file))
        {
            $line = fgets($file);
            if(empty($controllerClassName))
            {
                preg_match('/class[ \t]+(\w+)Controller/i', $line, $matchesClass);
                if(is_array($matchesClass) && ! empty($matchesClass[1]))
                    $controllerClassName = strtolower($matchesClass[1]);
            }

            preg_match('/public[ \t]+function[ \t]+action([A-Z]{1}[a-zA-Z0-9]+)[ \t]*\(/i', $line, $matchesAction);
            if(is_array($matchesAction) && ! empty($matchesAction[1]))
                $actions[] = strtolower($matchesAction[1]);
        }
        
        if(empty($controllerClassName))
            return false;
        else
            return array($controllerClassName=>$actions);
    }
    
    static public function getOperationName($controllerName, $actionName, $moduleName='')
    {
        return self::getTaskName($controllerName, $moduleName).self::SEPERATOR.strtolower($actionName);
    }
    
    static public function getRoleName($controllerName, $moduleName='')
    {
        return self::getTaskName($controllerName, $moduleName).self::SEPERATOR.'manager';
    }
    
    static public function getTaskName($controllerName, $moduleName='')
    {
        if(empty($moduleName) || is_numeric((string)$moduleName))
            return strtolower($controllerName);
        else
            return strtolower($moduleName).self::SEPERATOR.strtolower($controllerName);
    }
}
